/* 
 * [New BSD License (BSD 3-Clause License)]
 * Copyright (c) 2012, Max Bechtold
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     - Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     - Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     - Neither the name of the Rel2Xml Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
grammar CreateTable;

// Adaptated version of Danil Glinenko's CreateTable grammar 
// (http://codemate.wordpress.com/2009/02/06/writing-a-sql-parser/)
// using TPC-C like column data constraints (e.g. "unique within [1000]", see TPC-C spec)

options {
  language=Java;
}

// Make sure Lexer also finds its way into right package
@lexer::header
{
/* 
 * [New BSD License (BSD 3-Clause License)]
 * Copyright (c) 2012, Max Bechtold
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     - Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     - Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     - Neither the name of the Rel2Xml Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
	package maxbe.rel2xml.grammar;
} 

@header
{
/* 
 * [New BSD License (BSD 3-Clause License)]
 * Copyright (c) 2012, Max Bechtold
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     - Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     - Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     - Neither the name of the Rel2Xml Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
	package maxbe.rel2xml.grammar;
	
	import maxbe.rel2xml.generator.Column;
	import maxbe.rel2xml.generator.Table;
	import maxbe.rel2xml.generator.docs.DataType;
}

@members
{
  private int compositeKeyCount = 0;
  private int compositeForeignKeyCount = 0;
  private SchemaBuilder sb;
}

table_list [SchemaBuilder sb]
@init
{
 	this.sb = $sb;
}
@after
{
	sb.resolveReferences();
	sb.checkCompositeKeys();
}                                   
  :
  (table_def
                                 { sb.tables.add($table_def.tbl); }
  )*
  ;

table_def returns [Table tbl]
  :
  'create' 'table'
  tn=table_name
                                  { sb.checkTable($tn.text); }
                                  { $tbl = new Table(String.valueOf($tn.text)); }
  tel=table_element_list[$tbl]
  SEMICOLON
  ;

table_name
  :
  (schema DOT)? table
  ;

table_element_list [Table t]
  :
  LEFT_PAREN
  table_element[$t]
  (COMMA table_element[$t])*
  RIGHT_PAREN
  ;

table_element [Table t]
  :
  ( (PRIMARY_KEY | UNIQUE) => unique_constraint[$t]
  | (FOREIGN_KEY) => referential_constraint[$t]
  | column[$t]
  )                      
  ;
  
unique_constraint [Table t]
@init
{
  compositeKeyCount++;
}
  :
  ( PRIMARY_KEY
    LEFT_PAREN c=ID
                         { Column col = sb.getColumn($t, $c.text);
                           col.setCompositeKeyId(compositeKeyCount); }
    (COMMA c=ID
                         { Column col = sb.getColumn($t, $c.text);
                           col.setCompositeKeyId(compositeKeyCount); }
    )* RIGHT_PAREN
  | UNIQUE
    LEFT_PAREN c=ID
                         { Column col = sb.getColumn($t, $c.text);
                           col.setCompositeKeyId(compositeKeyCount); }
    (COMMA c=ID
                         { Column col = sb.getColumn($t, $c.text);
                           col.setCompositeKeyId(compositeKeyCount); }
    )* RIGHT_PAREN
  )
  ;
  
referential_constraint [Table t]
@init
{
	compositeForeignKeyCount++;
}
@after
{
	sb.checkEqualSizedReference($t);
}

  :
  FOREIGN_KEY
  LEFT_PAREN c=ID
                       { Column col = sb.getColumn($t, $c.text);
                         col.setCompositeForeignKeyId(compositeForeignKeyCount);
                         sb.refFrom.add(col); }
  (COMMA c=ID
                       { Column col = sb.getColumn($t, $c.text);
                         col.setCompositeForeignKeyId(compositeForeignKeyCount);
                         sb.refFrom.add(sb.getColumn($t, $c.text)); }
  )* RIGHT_PAREN
  'references' t1=ID
  LEFT_PAREN c=ID
                       { sb.refTo.add($t1.text + '.' + $c.text); }
  (COMMA c=ID
                       { sb.refTo.add($t1.text + '.' + $c.text); }
  )* RIGHT_PAREN
;

column [Table t]
@init
{
	Column col = null;
}
  :
  cn=column_name
  dtd=data_type_def
                                { col = new Column($cn.cn, $dtd.tp, $dtd.len1, $dtd.len2); }
                                { sb.addColumn($t, col); }
  (cc=column_constraint[col])
  ;
  
column_name returns [String cn]
  :
  ID
              { $cn = String.valueOf($ID.text); }
  ;

column_constraint [Column col]
  :
  ( 'not' 'null'
                                                                { $col.setNotNull(true); }
  | PRIMARY_KEY
                                                                { compositeKeyCount++;
                                                                  $col.setPrimaryKey(true);
                                                                  $col.setCompositeKeyId(compositeKeyCount * -1); }
  | UNIQUE
                                                                { compositeKeyCount++;
                                                                  $col.setUnique(true);
                                                                  $col.setCompositeKeyId(compositeKeyCount * -1); }
  )?
  (
  'references' t=table LEFT_PAREN cn=column_name RIGHT_PAREN
                                                                { sb.references.put($col, $t.text + '.' + $cn.text); }
  )?
  ;

data_type_def returns [DataType tp, Integer len1, Integer len2]
  :
  ( ('character' 'varying' | 'varchar')
    l=length_constraint
                                                  { $tp = DataType.VARCHAR; $len1 = $l.len1; }
  | ('character' | 'char')
    l=length_constraint
                                                  { $tp = DataType.CHAR; $len1 = $l.len1; }
  | ('integer' | 'int' | 'int4')
                                                  { $tp = DataType.INTEGER; }
  | ('decimal' | 'numeric' | 'dec')
    sl=sub_length_constraint 
                                                  { $tp = DataType.DECIMAL; $len1 = $sl.len1; $len2 = $sl.len2; }
  | ('datetime' | 'date' | 'time')
                                                  { $tp = DataType.DATETIME; }
  )
  ;

length_constraint returns [Integer len1]
  :
  LEFT_PAREN
  l1=NUMBER
                      { $len1 = Integer.valueOf($l1.text); }
  RIGHT_PAREN
  ;

sub_length_constraint returns [Integer len1, Integer len2]
  :
  LEFT_PAREN
  l1=NUMBER
                      { $len1 = Integer.valueOf($l1.text); }
  COMMA l2=NUMBER
                      { $len2 = Integer.valueOf($l2.text); }
  
  RIGHT_PAREN
  ;

schema
  :
  ID
  ;

table
  :
  ID
  ;


/*------------------------------------------------------------------
 * LEXER RULES
 *------------------------------------------------------------------*/
// SQL key words

UNIQUE : 'unique';
PRIMARY_KEY : 'primary key';
FOREIGN_KEY : 'foreign key';

LEFT_PAREN : '(';
RIGHT_PAREN : ')';
COMMA : ',';
SEMICOLON : ';';
DOT : '.';
NUMBER  : (DIGIT)+;
ID  : (('a'..'z'|'A'..'Z' | '_') ((DIGIT)*))+ ;
NEWLINE: '\r'? '\n' { $channel = HIDDEN; } ;
WS : ( '\t' | ' ' | '\r' | '\n' | '\u000C' )+ { $channel = HIDDEN; } ;
fragment DIGIT :   '0'..'9' ;

// Additional lexer rules to parse TPC-C like data generation constraints 

PERCENT : '%';
DOTDOT : '..';
QUOTE : '"';
STRING : QUOTE ('a'..'z'|'A'..'Z' | '_' | DIGIT)+ QUOTE;
MULTI_COMMENT options { greedy = false; } : '/*' .* '*/' NEWLINE? { skip(); };